/* boardview.cpp - this file is part of PixelMerge
 *
 * Copyright (C) 2010 Pascal Giard
 *
 * Author: Pascal Giard <evilynux@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Game view.
 */

#include "boardview.h"
#include "filelist.h"

BoardView::BoardView(u8 diff,
                     BoardSettings boardSettings,
                     UserSettings * userSettings)
  : GuiWindow(screenwidth, screenheight) {
  gameDiff = diff;
  settings = boardSettings;
  user = userSettings;
  pixelSize = 0;
  nbrSteps = 0;
  gameComplete = false;
  snprintf(stepChars, sizeof(stepChars), "Step X/Y");
  snprintf(perfChars, sizeof(perfChars), "Best perf. yet");

  trigA = new GuiTrigger();
  trigA->SetSimpleTrigger(-1, WPAD_BUTTON_A | WPAD_CLASSIC_BUTTON_A,
                          PAD_BUTTON_A);
  trigHome = new GuiTrigger();
  trigHome->SetButtonOnlyTrigger(-1, 
                                 WPAD_BUTTON_HOME | WPAD_CLASSIC_BUTTON_HOME, 0);

  trigPink = new GuiTrigger();
  trigPink->SetButtonOnlyTrigger(-1, WPAD_BUTTON_UP, 0);
  trigDarkBlue = new GuiTrigger();
  trigDarkBlue->SetButtonOnlyTrigger(-1, WPAD_BUTTON_DOWN, 0);
  trigYellow = new GuiTrigger();
  trigYellow->SetButtonOnlyTrigger(-1, WPAD_BUTTON_PLUS, 0);
  trigRed = new GuiTrigger();
  trigRed->SetButtonOnlyTrigger(-1, WPAD_BUTTON_MINUS, 0);
  trigLightBlue = new GuiTrigger();
  trigLightBlue->SetButtonOnlyTrigger(-1, WPAD_BUTTON_1, 0);
  trigGreen = new GuiTrigger();
  trigGreen->SetButtonOnlyTrigger(-1, WPAD_BUTTON_2, 0);

  btnSoundOver = new GuiSound(button_over_pcm, button_over_pcm_size, SOUND_PCM);
  btnOutline = new GuiImageData(button_png);
  btnOutlineOver = new GuiImageData(button_over_png);

  model = new BoardModel(settings.nbrRows);
  pixelSize = settings.size/model->nbrRows;

  InitGameUI();
  InitGameButtons();

    // Some crazy math to make the layout look good at all 3 difficulties.
  u16 boardWidth = model->nbrRows*pixelSize;
  u16 boardOffsetX = (screenwidth - boardWidth) >> 4;
  u16 pixelArea = screenwidth - (boardOffsetX + boardWidth);
  u16 pixelWidth = pixelBtn[PINK]->GetWidth();
  u16 space = (pixelArea - (pixelWidth<<1))/5;
  u16 pxlPosX1 = (boardOffsetX + boardWidth) + (space<<1);
  u16 pxlPosX2 = pxlPosX1 + pixelWidth + space;
  f32 menuBtnScale = 1.0 - ((f32)gameDiff)/10;
  u16 menuPosX = (boardOffsetX+boardWidth+(pixelArea>>1)) - (screenwidth>>1);

  exitBtnTxt = new GuiText(_("Quit"), 22, (GXColor){255, 255, 255, 255});
  exitBtnImg = new GuiImage(btnOutline);
  exitBtnImgOver = new GuiImage(btnOutlineOver);
  exitBtn = new GuiButton(btnOutline->GetWidth(), btnOutline->GetHeight());
  exitBtn->SetAlignment(ALIGN_CENTRE, ALIGN_BOTTOM);
  exitBtn->SetScale(menuBtnScale);
  exitBtn->SetPosition(menuPosX, -10);
  exitBtn->SetLabel(exitBtnTxt);
  exitBtn->SetImage(exitBtnImg);
  exitBtn->SetImageOver(exitBtnImgOver);
  exitBtn->SetSoundOver(btnSoundOver);
  exitBtn->SetTrigger(trigA);
  exitBtn->SetTrigger(trigHome);
  exitBtn->SetEffectGrow();

  newGameBtnTxt = new GuiText(_("New Game"), 22, (GXColor){255, 255, 255, 255});
  newGameBtnImg = new GuiImage(btnOutline);
  newGameBtnImgOver = new GuiImage(btnOutlineOver);
  newGameBtn = new GuiButton(btnOutline->GetWidth(), btnOutline->GetHeight());
  newGameBtn->SetAlignment(ALIGN_CENTRE, ALIGN_BOTTOM);
  newGameBtn->SetScale(menuBtnScale);
  newGameBtn->SetPosition(menuPosX,
                         -20-menuBtnScale*btnOutline->GetHeight());
  newGameBtn->SetLabel(newGameBtnTxt);
  newGameBtn->SetImage(newGameBtnImg);
  newGameBtn->SetImageOver(newGameBtnImgOver);
  newGameBtn->SetSoundOver(btnSoundOver);
  newGameBtn->SetTrigger(trigA);
  newGameBtn->SetEffectGrow();

  pixelBtn[PINK]->SetAlignment(ALIGN_LEFT, ALIGN_TOP);
  pixelBtn[PINK]->SetPosition(pxlPosX1, 130);
  pixelBtn[PINK]->SetSoundOver(btnSoundOver);
  pixelBtn[PINK]->SetTrigger(trigA);
  pixelBtn[PINK]->SetTrigger(trigPink);
  pixelBtn[PINK]->SetEffectGrow();

  pixelBtn[DARK_BLUE]->SetAlignment(ALIGN_LEFT, ALIGN_TOP);
  pixelBtn[DARK_BLUE]->SetPosition(pxlPosX2, 130);
  pixelBtn[DARK_BLUE]->SetSoundOver(btnSoundOver);
  pixelBtn[DARK_BLUE]->SetTrigger(trigA);
  pixelBtn[DARK_BLUE]->SetTrigger(trigDarkBlue);
  pixelBtn[DARK_BLUE]->SetEffectGrow();

  pixelBtn[YELLOW]->SetAlignment(ALIGN_LEFT, ALIGN_TOP);
  pixelBtn[YELLOW]->SetPosition(pxlPosX1, 202);
  pixelBtn[YELLOW]->SetSoundOver(btnSoundOver);
  pixelBtn[YELLOW]->SetTrigger(trigA);
  pixelBtn[YELLOW]->SetTrigger(trigYellow);
  pixelBtn[YELLOW]->SetEffectGrow();

  pixelBtn[RED]->SetAlignment(ALIGN_LEFT, ALIGN_TOP);
  pixelBtn[RED]->SetPosition(pxlPosX2, 202);
  pixelBtn[RED]->SetSoundOver(btnSoundOver);
  pixelBtn[RED]->SetTrigger(trigA);
  pixelBtn[RED]->SetTrigger(trigRed);
  pixelBtn[RED]->SetEffectGrow();

  pixelBtn[LIGHT_BLUE]->SetAlignment(ALIGN_LEFT, ALIGN_TOP);
  pixelBtn[LIGHT_BLUE]->SetPosition(pxlPosX1, 274);
  pixelBtn[LIGHT_BLUE]->SetSoundOver(btnSoundOver);
  pixelBtn[LIGHT_BLUE]->SetTrigger(trigA);
  pixelBtn[LIGHT_BLUE]->SetTrigger(trigLightBlue);
  pixelBtn[LIGHT_BLUE]->SetEffectGrow();

  pixelBtn[GREEN]->SetAlignment(ALIGN_LEFT, ALIGN_TOP);
  pixelBtn[GREEN]->SetPosition(pxlPosX2, 274);
  pixelBtn[GREEN]->SetSoundOver(btnSoundOver);
  pixelBtn[GREEN]->SetTrigger(trigA);
  pixelBtn[GREEN]->SetTrigger(trigGreen);
  pixelBtn[GREEN]->SetEffectGrow();

  this->Append(stepTxt);
  if( pxlMgSettings->user.record[gameDiff] != 0) {
    this->Append(perfTxt);
  }
  this->Append(exitBtn);
  this->Append(newGameBtn);
  for(u16 i = 0; i < model->boardSize; i++) {
    this->Append(boardImg[i]);
  }
  for(u16 i = 0; i < NBR_PIXELS; i++) {
    this->Append(pixelBtn[i]);
  }
}

BoardView::~BoardView() {
  delete trigA;
  delete trigHome;
  delete trigPink;
  delete trigDarkBlue;
  delete trigYellow;
  delete trigRed;
  delete trigLightBlue;
  delete trigGreen;
  delete btnOutline;
  delete btnOutlineOver;
  delete btnSoundOver;
  for(u8 i = 0; i < NBR_PIXELS; i++) {
    delete pixelBtn[i];
    delete pixelBtnImg[i];
    delete pixelBtnImgData[i];
    delete pixelImg[i];
  }
  for(u16 i = 0; i < model->boardSize; i++) {
    delete boardImg[i];
  }
  delete stepTxt;
  delete perfTxt;
  delete newGameBtn;
  delete newGameBtnTxt;
  delete newGameBtnImg;
  delete newGameBtnImgOver;
  delete exitBtnTxt;
  delete exitBtnImg;
  delete exitBtnImgOver;
  delete exitBtn;
  delete model;
}

void BoardView::InitGameUI() {
  u16 boardWidth = model->nbrRows*pixelSize;
  u16 posX = (screenwidth - boardWidth) >> 4;
  u16 posY = (screenheight - boardWidth) >> 2;
  u16 boardOffsetX = (screenwidth - boardWidth) >> 4;
  u16 rightArea = screenwidth - (boardOffsetX + boardWidth);
  u16 textPosX = (boardOffsetX+boardWidth+(rightArea>>1)) - (screenwidth>>1);

  // Pixel colors
  pixelColor[PINK] = (GXColor){255, 0, 255, DEF_ALPHA};
  pixelColor[DARK_BLUE] = (GXColor){0, 0, 127, DEF_ALPHA};
  pixelColor[YELLOW] = (GXColor){255, 255, 0, DEF_ALPHA};
  pixelColor[RED] = (GXColor){255, 0, 0, DEF_ALPHA};
  pixelColor[LIGHT_BLUE] = (GXColor){0, 255, 255, DEF_ALPHA};
  pixelColor[GREEN] = (GXColor){0, 127, 0, DEF_ALPHA};
  // Board pixel images
  pixelImg[PINK] = new GuiImage(pixelSize, pixelSize, pixelColor[PINK]);
  pixelImg[DARK_BLUE] =
    new GuiImage(pixelSize, pixelSize, pixelColor[DARK_BLUE]);
  pixelImg[YELLOW] = new GuiImage(pixelSize, pixelSize, pixelColor[YELLOW]);
  pixelImg[RED] = new GuiImage(pixelSize, pixelSize, pixelColor[RED]);
  pixelImg[LIGHT_BLUE] =
    new GuiImage(pixelSize, pixelSize, pixelColor[LIGHT_BLUE]);
  pixelImg[GREEN] = new GuiImage(pixelSize, pixelSize, pixelColor[GREEN]);

  nbrSteps = 0;
  snprintf(stepChars, sizeof(stepChars), "%s %d/%d", _("Step"), nbrSteps,
           settings.nbrSteps);
  stepTxt = new GuiText(stepChars, 24, (GXColor){255, 255, 255, 255});
  stepTxt->SetAlignment(ALIGN_CENTRE, ALIGN_TOP);
  stepTxt->SetPosition(textPosX, posY << 1);

  snprintf(perfChars, sizeof(perfChars), "%s %d",
           _("Record:"), pxlMgSettings->user.record[gameDiff]);
  perfTxt = new GuiText(perfChars, 24, (GXColor){255, 255, 255, 255});
  perfTxt->SetAlignment(ALIGN_CENTRE, ALIGN_TOP);
  perfTxt->SetPosition(textPosX, (posY << 1)+40);

  boardImg.resize(model->boardSize);
  for(u16 i = 0; i < model->boardSize; i++) {
    u8 color = model->pixels[i].color;
    boardImg[i] = new GuiImage(pixelImg[color]->GetImage(),
                               pixelSize, pixelSize);
    boardImg[i]->SetAlignment(ALIGN_LEFT, ALIGN_TOP);
    boardImg[i]->SetPosition(posX+(i%model->nbrRows)*pixelSize,
                             posY+(i/model->nbrRows)*pixelSize);
  }
}

void BoardView::InitGameButtons() {
  // Pixel buttons
  pixelBtnImgData[PINK] = new GuiImageData(button_pink_png);
  pixelBtnImgData[DARK_BLUE] = new GuiImageData(button_dark_blue_png);
  pixelBtnImgData[YELLOW] = new GuiImageData(button_yellow_png);
  pixelBtnImgData[RED] = new GuiImageData(button_red_png);
  pixelBtnImgData[LIGHT_BLUE] = new GuiImageData(button_light_blue_png);
  pixelBtnImgData[GREEN] = new GuiImageData(button_green_png);

  for(u8 i = 0; i < NBR_PIXELS; i++) {
    pixelBtnImg[i] = new GuiImage(pixelBtnImgData[i]);
    pixelBtn[i] = new GuiButton(pixelBtnImg[i]->GetWidth(),
                                pixelBtnImg[i]->GetHeight());
    pixelBtn[i]->SetImage(pixelBtnImg[i]);
  }
}

void BoardView::NewGame() {
  gameComplete = false;
  model->newGame();
  for(u16 i = 0; i < model->boardSize; i++) {
    u8 color = model->pixels[i].color;
    boardImg[i]->SetImage(pixelImg[color]->GetImage(),
                          pixelSize, pixelSize);
  }
  nbrSteps = 0;

  // Update Step text
  snprintf(stepChars, sizeof(stepChars), "%s %d/%d", _("Step"), nbrSteps,
           settings.nbrSteps);
  stepTxt->SetText(stepChars);

  // Update Best performance text
  snprintf(perfChars, sizeof(perfChars), "%s %d",
           _("Record:"), pxlMgSettings->user.record[gameDiff]);
  perfTxt->SetText(perfChars);
  if( pxlMgSettings->user.record[gameDiff] != 0 ) {
    // Removed then re-added if already there.
    this->Append(perfTxt);
  }
}

void BoardView::Exit() {

}

void BoardView::UpdateBoard(u8 color) {
  // No action required if...
  // 1) The player failed the game;
  // 2) The player succeeded;
  // 3) The new color is the same as the old one.
  if(nbrSteps == settings.nbrSteps or gameComplete or
     color == model->pixels[0].color) {
    return;
  }

  nbrSteps++;
  // Update model
  model->updateColor(color);
  // Update view
  for(u16 i = 0; i < model->boardSize; i++) {
    u8 color = model->pixels[i].color;
    boardImg[i]->SetImage(pixelImg[color]->GetImage(),
                          pixelSize, pixelSize);
  }

  // Update Step text
  snprintf(stepChars, sizeof(stepChars), "%s %d/%d", _("Step"), nbrSteps,
           settings.nbrSteps);
  stepTxt->SetText(stepChars);

  CheckCompletion();
}

void BoardView::CheckCompletion() {
  int choice;
  char successMsg[100];
  char successTitle[20];
  u8 newRecord = 0;

  if(model->checkCompletion()) {
    gameComplete = true;
    // I'm forced to split the string for accents to work in snprintf :-(
    snprintf(successMsg, sizeof(successMsg), "%s %d %s",
             _("Game completed in"), nbrSteps, _("steps, congratulations!"));
    if(pxlMgSettings->user.record[gameDiff] == 0 ||
       nbrSteps < pxlMgSettings->user.record[gameDiff]) {
      snprintf(successTitle, sizeof(successTitle), "%s", _("New record!"));
      newRecord = 1;
    } else {
      snprintf(successTitle, sizeof(successTitle), "%s", _("Success!"));
    }
    choice =
      WindowPrompt(successTitle, successMsg, _("New Game"), _("Cancel"));
  } else if(nbrSteps == settings.nbrSteps) {
    choice =
      WindowPrompt(_("You failed!"),
                   _("You did not complete the game within the"
                     " maximum number of allowed steps."),
                   _("New Game"),
                   _("Cancel"));
  } else {
    return; // Game not completed but maximum number of steps not reached.
  }

  if(newRecord) {
    if(!pxlMgSettings->user.autoSave) {
      newRecord = 0;
      newRecord = WindowPrompt(_("Save new record?"),
                               "", _("Yes"), _("No"));
    }
    if(pxlMgSettings->user.autoSave || newRecord) {
      pxlMgSettings->user.record[gameDiff] = nbrSteps;
      pxlMgSettings->save();
    }
  }

  if(choice) {
    NewGame();
  }
}

int BoardView::Run() {
  if(exitBtn->GetState() == STATE_CLICKED) {
    return MENU_START;
  } else if(newGameBtn->GetState() == STATE_CLICKED) {
    HaltGui();
    NewGame();
    newGameBtn->SetState(STATE_DEFAULT, -1);
    ResumeGui();
  } else {
    for(u8 i = 0; i < NBR_PIXELS; i++) {
      if(pixelBtn[i]->GetState() == STATE_CLICKED) {
        HaltGui();
        UpdateBoard(i);
        pixelBtn[i]->SetState(STATE_DEFAULT, -1);
        ResumeGui();
        break;
      }
    }
  }
  return MENU_NONE;
}
